import base64
import re
import socket
from collections import OrderedDict
from io import BytesIO
import json
import ssl

import ctypes

class Response:
    def __init__(self, code: int, msg: str, headers: OrderedDict, body: bytes):
        self.code = code
        self.msg = msg
        self.headers = headers
        self.body = body
    @property
    def content(self):
        return self.body
    @property
    def text(self):
        return self.body.decode()
    @property
    def json(self):
        return json.loads(self.body.decode())

def request(host :str, port = 80, path = '/', query = {}, /, headers={}, data :dict|None=None, is_https = False):
    if len(query) > 0:
        path += '?' + '&'.join([f'{k}={v}' for k,v in query.items()])
    local_headers = {
        'Host': f'{host}:{port}',
        'Accept-Encoding': 'identity',
        'User-Agent': 'test',
        'Connection': 'close',
    }
    method = 'GET'
    in_data = b''
    if data != None:
        method = 'POST'
        local_headers['Content-Type'] = 'application/x-www-form-urlencoded'
        in_data = '&'.join([f'{k}={v}' for k,v in data.items()]).encode()
        local_headers['Content-Length'] = len(in_data)
    headers = local_headers | headers
    datas = [f'{method} {path} HTTP/1.1']
    datas += [f'{k}: {v}' for k, v in headers.items()]
    data_str = ''
    for data in datas:
        data_str += data + '\r\n'
    data_str += '\r\n'
    data_bytes = data_str.encode() + in_data
    if is_https:
        context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
        # print(f'min ver: {context.minimum_version}')
        # print(f'max ver: {context.maximum_version}')
        context.minimum_version = ssl.TLSVersion.TLSv1_2
        context.maximum_version = ssl.TLSVersion.TLSv1_2
        context.check_hostname = False
        context.verify_mode = ssl.CERT_NONE
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        with s:
            s.settimeout(5)

            s.connect((host, port))
            sslobj = context._wrap_socket(s, False, None, owner=s, session=None)
            sslobj.write(data_bytes)
            ret_bytes = b''
            while True:
                t = sslobj.read(4096)
                if len(t) < 1:
                    break
                ret_bytes += t
            with BytesIO(ret_bytes) as b:
                t = b.readline()
                m = re.match(b'^HTTP/1.1 (\\d+) (.+)\r\n$', t)
                assert m != None
                code = int(m.group(1))
                msg = m.group(2)
                hdrs = OrderedDict()
                while True:
                    t = b.readline()
                    assert t[-2:] == b'\r\n'
                    t = t[:-2]
                    if len(t) < 1:
                        break
                    m = re.match(b'^([^:]+):\\s*(.*)$', t)
                    assert m != None, f'{t} not match'
                    hdrs[m.group(1).decode()] = m.group(2).decode()
                assert 'Content-Length' in hdrs, 'headers: \n' + '\n'.join([f'{k}: {v}' for k, v in hdrs.items()])
                n = int(hdrs['Content-Length'])
                content = b.read(n)
                assert len(content) == n
            return Response(code, msg, hdrs, content)
    else:
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        with s:
            s.settimeout(5)
            s.connect((host, port))
            s.send(data_bytes)
            ret_bytes = b''
            while True:
                t = s.recv(4096)
                if len(t) < 1:
                    break
                ret_bytes += t
            with BytesIO(ret_bytes) as b:
                t = b.readline()
                m = re.match(b'^HTTP/1.1 (\\d+) (.+)\r\n$', t)
                assert m != None
                code = int(m.group(1))
                msg = m.group(2)
                hdrs = OrderedDict()
                while True:
                    t = b.readline()
                    assert t[-2:] == b'\r\n'
                    t = t[:-2]
                    if len(t) < 1:
                        break
                    m = re.match(b'^([^:]+):\\s*(.*)$', t)
                    assert m != None, f'{t} not match'
                    hdrs[m.group(1).decode()] = m.group(2).decode()
                assert 'Content-Length' in hdrs, 'headers: \n' + '\n'.join([f'{k}: {v}' for k, v in hdrs.items()])
                n = int(hdrs['Content-Length'])
                content = b.read(n)
                assert len(content) == n
            return Response(code, msg, hdrs, content)
    raise Exception('error')

def main():
    # r = request('127.0.0.1', 8888)
    # print(r.text)
    # r = request('127.0.0.1', 8888, '/test', {'a':1,'b':2,'c':3})
    # print(r.text)
    # r = request('127.0.0.1', 8888, '/test', {'a':1,'b':2,'c':3}, data={'e':4,'f':5,'g':6,'h':7,'i':8})
    # print(r.text)
    # r = request('www.baidu.com', 80)
    r = request('www.baidu.com', 443, is_https=True)
    # print(r.text)
    print(len(r.text))
    print(len(r.text.encode()))
    print(r.headers['Content-Length'])

if __name__=='__main__':
    main()